
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "oled_api.h"
#include "fontslib.h"

static void oled_init(void);
static void oled_on_off(uint8_t onoff);
static void oled_set_light(uint8_t level);
static void oled_scroll(uint8_t start, uint8_t end, uint8_t div_time, uint8_t lr);
static void oled_scoll_close(void);
static void oled_clean_gram(uint8_t data);
static void oled_local_refresh_gram(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t mode);
static void oled_all_refresh_gram(uint8_t mode);
static void oled_drow_point(uint8_t x, uint8_t y, uint8_t t);
static void oled_drow_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode);
static void oled_drow_rect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode);
static void oled_show_char(uint8_t x, uint8_t y, uint8_t ch, uint8_t size, uint8_t mode);
static void oled_show_number(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size, uint8_t mode);
static void oled_show_string(uint8_t x, uint8_t y, const uint8_t *p, size_t size, uint8_t mode);
static void oled_draw_bmp(uint8_t x, uint8_t y, uint8_t w, uint8_t l, uint8_t model, void *bmp);
static void oled_draw_bmp_point(uint8_t x, uint8_t y, uint8_t w, uint8_t l, uint8_t mode, void *bmp);
static void oled_draw_bmp_reversal(uint8_t x, uint8_t y, uint8_t w, uint8_t l, uint8_t model, void *bmp);

Oled_api oled = {
    .init               = oled_init,
    .display_onoff      = oled_on_off,
    .set_light_level    = oled_set_light,
    .scoll_close        = oled_scoll_close,
    .scroll_start       = oled_scroll,

    .drow_point         = oled_drow_point,
    .drow_line          = oled_drow_line,
    .draw_rect          = oled_drow_rect,
    .show_char          = oled_show_char,
    .show_number        = oled_show_number,
    .show_string        = oled_show_string,
    .draw_bmp           = oled_draw_bmp,
    .draw_bmp_point     = oled_draw_bmp_point,
    .draw_bmp_reversal  = oled_draw_bmp_reversal,

    .clean_gram         = oled_clean_gram,
    .refresh_local      = oled_local_refresh_gram,
    .refresh_all        = oled_all_refresh_gram,
};

i2c_port_t i2c_num = I2C_EXAMPLE_MASTER_NUM;

static esp_err_t oled_delay_ms(uint32_t time)
{
    vTaskDelay(time / portTICK_RATE_MS);
    return ESP_OK;
}

static uint32_t oled_pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;
    while (n--)
        result *= m;
    return result;
}

static void oled_wr_byte(i2c_port_t i2c_num, uint8_t oled_byte, uint8_t oled_type)
{
    if (oled_type)
        faster_iic_write_byte(oled_byte);
    else
        faster_iic_write_cmd(oled_byte);
}

static void oled_clear(i2c_port_t i2c_num)
{
    uint8_t N_Page, N_Row;
    for (N_Page = 0; N_Page < 8; N_Page++)
    {
        oled_wr_byte(i2c_num, 0xb0 + N_Page, OLED_CMD); // 从0～7页依次写入
        oled_wr_byte(i2c_num, 0x00, OLED_CMD);          // 列低地址
        oled_wr_byte(i2c_num, 0x10, OLED_CMD);          // 列高地址
    }
    for (N_Row = 0; N_Row < 128; N_Row++)
        oled_wr_byte(i2c_num, 0x00, OLED_DATA);
}

static void oled_gpio_rst_config()
{
    gpio_config_t io_conf;

    //config as out put mode
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = OLED_PIN_SEL;
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);

    gpio_set_level(OLED_GPIO_PIN_RST, 0);
    oled_delay_ms(200);
    gpio_set_level(OLED_GPIO_PIN_RST, 1);
    oled_delay_ms(100);
}

/******************************************** 功能接口 *************************************************/
/***********************************************************************
 *  函数名: oled_init
 *  描述：初始化oled
 *  参数：void
 *  返回值： void
 ***********************************************************************/
static void oled_init(void)
{
    iic_master_gpio_init();
    oled_delay_ms(100);

    //pin_ret
    oled_gpio_rst_config();
    oled_clear(i2c_num);

    oled_wr_byte(i2c_num, 0xAE, OLED_CMD); //display off
    oled_wr_byte(i2c_num, 0x20, OLED_CMD); //Set Memory Addressing Mode
    oled_wr_byte(i2c_num, 0x02, OLED_CMD); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
    oled_wr_byte(i2c_num, 0xb0, OLED_CMD); //Set Page Start Address for Page Addressing Mode,0-7
    oled_wr_byte(i2c_num, 0xc8, OLED_CMD); //Set COM Output Scan Direction
    oled_wr_byte(i2c_num, 0x00, OLED_CMD); //---set low column address
    oled_wr_byte(i2c_num, 0x10, OLED_CMD); //---set high column address
    oled_wr_byte(i2c_num, 0x40, OLED_CMD); //--set start line address
    oled_wr_byte(i2c_num, 0x81, OLED_CMD); //--set contrast control register
    oled_wr_byte(i2c_num, 0xff, OLED_CMD); //亮度调节 0x00~0xff
    oled_wr_byte(i2c_num, 0xa1, OLED_CMD); //--set segment re-map 0 to 127
    oled_wr_byte(i2c_num, 0xC0, OLED_CMD); //--set normal display
    oled_wr_byte(i2c_num, 0xa8, OLED_CMD); //--set multiplex ratio(1 to 64)
    oled_wr_byte(i2c_num, 0x3F, OLED_CMD); //
    oled_wr_byte(i2c_num, 0xa4, OLED_CMD); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
    oled_wr_byte(i2c_num, 0xd3, OLED_CMD); //-set display offset
    oled_wr_byte(i2c_num, 0x00, OLED_CMD); //-not offset
    oled_wr_byte(i2c_num, 0xd5, OLED_CMD); //--set display clock divide ratio/oscillator frequency
    oled_wr_byte(i2c_num, 0xf0, OLED_CMD); //--set divide ratio
    oled_wr_byte(i2c_num, 0xd9, OLED_CMD); //--set pre-charge period
    oled_wr_byte(i2c_num, 0x22, OLED_CMD); //
    oled_wr_byte(i2c_num, 0xda, OLED_CMD); //--set com pins hardware configuration
    oled_wr_byte(i2c_num, 0x12, OLED_CMD);
    oled_wr_byte(i2c_num, 0xdb, OLED_CMD); //--set vcomh
    oled_wr_byte(i2c_num, 0x20, OLED_CMD); //0x20,0.77xVcc
    oled_wr_byte(i2c_num, 0x8d, OLED_CMD); //--set DC-DC enable
    oled_wr_byte(i2c_num, 0x14, OLED_CMD); //
    oled_wr_byte(i2c_num, 0xaf, OLED_CMD); //--turn on oled panel
    oled_wr_byte(i2c_num, 0xAF, OLED_CMD); // 开启OLED面板显示
    printf("oled_init ok\n");
}

/***********************************************************************
 *  函数名: oled_on_off
 *  描述：是否显示oled，可用于节能
 *  参数：uint8_t switch 开关(1:on 0:off)
 *  返回值： void
 ***********************************************************************/
static void oled_on_off(uint8_t onoff)
{
    if (onoff)
    {
        oled_wr_byte(i2c_num, 0xAF, OLED_CMD); //display on
    }
    else
    {
        oled_wr_byte(i2c_num, 0xAE, OLED_CMD); //display off
    }
}

/***********************************************************************
 *  函数名: oled_set_light
 *  描述：设置oled亮度
 *  参数：uint8_t level 亮度等级(1,2,3,4,5)
 *  返回值： void
 ***********************************************************************/
static void oled_set_light(uint8_t level)
{
    oled_wr_byte(i2c_num, 0x81, OLED_CMD);
    if (level == 1)
        oled_wr_byte(i2c_num, 0x01, OLED_CMD); //亮度调节 0x00~0xff
    if (level == 2)
        oled_wr_byte(i2c_num, 0x19, OLED_CMD); //亮度调节 0x00~0xff
    if (level == 3)
        oled_wr_byte(i2c_num, 0x2b, OLED_CMD); //亮度调节 0x00~0xff
    if (level == 4)
        oled_wr_byte(i2c_num, 0xb8, OLED_CMD); //亮度调节 0x00~0xff
    if (level == 5)
        oled_wr_byte(i2c_num, 0xff, OLED_CMD); //亮度调节 0x00~0xff
}

/***********************************************************************
 *  函数名: oled_scroll
 *  描述：开启屏幕滚动
 *  参数：uint8_t start 起始页
 *  参数：uint8_t end   结束页
 *  参数：uint8_t div_time 滚动间隔时间
 *  参数：uint8_t lr    水平向左或者右滚动 26/27
 *  返回值： void
 ***********************************************************************/
static void oled_scroll(uint8_t start, uint8_t end, uint8_t div_time, uint8_t lr)
{
    oled_wr_byte(i2c_num, 0x2E, OLED_CMD);     //关闭滚动
    oled_wr_byte(i2c_num, lr, OLED_CMD);       //水平向左或者右滚动 26/27
    oled_wr_byte(i2c_num, 0x00, OLED_CMD);     //虚拟字节
    oled_wr_byte(i2c_num, start, OLED_CMD);    //起始页 0
    oled_wr_byte(i2c_num, div_time, OLED_CMD); //滚动时间间隔
    oled_wr_byte(i2c_num, end, OLED_CMD);      //终止页 7
    oled_wr_byte(i2c_num, 0x00, OLED_CMD);     //虚拟字节
    oled_wr_byte(i2c_num, 0xFF, OLED_CMD);     //虚拟字节
    oled_wr_byte(i2c_num, 0x2F, OLED_CMD);     //开启滚动
}

/***********************************************************************
 *  函数名: oled_scoll_close
 *  描述：关闭水平滚动
 *  参数：void
 *  返回值： void
 ***********************************************************************/
static void oled_scoll_close(void)
{
    oled_wr_byte(i2c_num, 0x2E, OLED_CMD); //关闭滚动
}

/***********************************************************************
 *  函数名: oled_clean_gram
 *  描述：将缓冲区写满某个数值，一般用于清屏!!!
 *  参数：uint8_t data  要填充的值
 *  返回值： void
 ***********************************************************************/
static void oled_clean_gram(uint8_t data)
{
    uint8_t i, n;
    for (i = 0; i < SCREAM_ARRAY_H; i++)
    {
        oled_wr_byte(i2c_num, 0xb0 + i, OLED_CMD); //设置页地址（0~7）
        oled_wr_byte(i2c_num, 0x00, OLED_CMD);     //设置显示位置—列低地址
        oled_wr_byte(i2c_num, 0x10, OLED_CMD);     //设置显示位置—列高地址
        for (n = 0; n < SCREAM_SIZE_W; n++)
        {
            OLED_GRAMS[n][i] = data;
        }
    }
}

/***********************************************************************
 *  函数名: oled_local_refresh_gram
 *  描述：在缓冲区的局部数据刷新到屏幕
 *  参数：uint8_t x0 起始x坐标
 *  参数：uint8_t y0 起始y坐标
 *  参数：uint8_t x1 结束x坐标
 *  参数：uint8_t y1 结束y坐标
 *  参数：uint8_t mode 是否清空已刷新的区域(1:clean 0:no)
 *  返回值： void
 ***********************************************************************/
static void oled_local_refresh_gram(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t mode)
{
    uint8_t n, m;
    x0 = (x0 > 0) ? (x0 - 1) : 0;
    for (n = y0; n < y1; n++)
    {
        faster_iic_write_cmd(0x20);
        faster_iic_write_cmd(0);

        faster_iic_write_cmd(0xb0 + n);                  /* 设置要Y */
        faster_iic_write_cmd(((x0 & 0xF0) >> 4) | 0x10); /* 设置x */
        faster_iic_write_cmd(((x0 & 0x0F) | 0x01) - 1);

        faster_iic_start();
        faster_iic_write_byte(0x78);
        faster_iic_wait_ack();
        faster_iic_write_byte(0x40);
        faster_iic_wait_ack();
        for (m = x0; m < x1; m++)
        {
            faster_iic_write_byte(OLED_GRAM[m][n]);
            if (mode)
                OLED_GRAM[m][n] = 0;

            IIC_SCL_SET();
            os_delay_us(3);
            IIC_SCL_CLEAR();
        }
        faster_iic_stop();
    }
}

/***********************************************************************
 *  函数名: oled_refresh_gram
 *  描述：在缓冲区的全部数据刷新到屏幕
 *  参数：uint8_t mode 是否清空已刷新的区域(1:clean 0:no)
 *  返回值： void
 ***********************************************************************/
static void oled_all_refresh_gram(uint8_t mode)
{
    oled_local_refresh_gram(0, 0, X_WIDTH, PAGE, mode);
}

/******************************************** 显示接口 *************************************************/

/***********************************************************************
 *  函数名: oled_drow_point
 *  描述：在缓冲区打印一个像素
 *  参数：uint8_t x 横坐标
 *  参数：uint8_t y 纵坐标(1-64)
 *  参数：uint8_t mode 显示模式(1:黑底白字 0：白底黑字)
 *  返回值： void
 ***********************************************************************/
static void oled_drow_point(uint8_t x, uint8_t y, uint8_t t)
{
    uint8_t pos, bx, temp = 0;
    if (x > (X_WIDTH - 1) || y > (Y_WIDTH - 1))
        return;
    pos = 7 - y / 8;
    bx = y % 8;
    temp = 1 << (7 - bx);

    if (t)
        OLED_GRAM[x][pos] |= temp;
    else
        OLED_GRAM[x][pos] &= ~temp;
}

/***********************************************************************
 *  函数名: oled_drow_line
 *  描述：画一条直线
 *  参数：uint8_t x1、y1: （x1,y1)点1
 *  参数：uint8_t x2、y2: (x2,y2)点2
 *  参数：uint8_t mode 显示模式(1:黑底白字 0：白底黑字)
 *  返回值： void
 ***********************************************************************/
static void oled_drow_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode)
{
    float x = 0.0f, y = 0.0f;
    int8_t minx = x1, miny = y1; //p1
    int8_t maxx = x2, maxy = y2; //p2
    if (x1 == x2)
    {
        if (y1 > y2)
        {
            minx = x2, miny = y2;
            maxx = x1, maxy = y1;
        }
    }
    else if (x1 > x2)
    {
        minx = x2, miny = y2;
        maxx = x1, maxy = y1;
    }

    if (minx == maxx)
    {
        for (y = miny; y <= maxy; y++)
        {
            oled_drow_point(minx, y, mode);
        }
    }
    else
    {
        for (x = minx; x <= maxx; x++)
        {
            y = (maxy - miny) * (x - maxx) / (maxx - minx) + maxy;
            oled_drow_point(x, y, mode);
        }
    }
}

/***********************************************************************
 *  函数名: oled_drow_rect
 *  描述：画一条矩形
 *  参数：uint8_t x1、y1: （x1,y1)点1
 *  参数：uint8_t x2、y2: (x2,y2)点2
 *  参数：uint8_t mode 显示模式(1:黑底白字 0：白底黑字)
 *  返回值： void
 ***********************************************************************/
static void oled_drow_rect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode)
{
    int8_t minx = x1, miny = y1;
    int8_t maxx = x2, maxy = y2;
    if (x1 > x2)
    {
        minx = x2, miny = y2;
        maxx = x1, maxy = y1;
    }

    oled_drow_line(minx, miny, maxx, miny, mode);
    oled_drow_line(maxx, miny, maxx, maxy, mode);
    oled_drow_line(minx, miny, minx, maxy, mode);
    oled_drow_line(minx, maxy, maxx, maxy, mode);
}

/***********************************************************************
 *  函数名: oled_show_char
 *  描述：在缓冲区打印一个字符
 *  参数：uint8_t x 横坐标
 *  参数：uint8_t y 纵坐标(1-64)
 *  参数: uint8_t ch 要打印的字符
 *  参数: uint8_t size 字体大小
 *  参数：uint8_t mode 显示模式(1:黑底白字 0：白底黑字)
 *  返回值： void
 ***********************************************************************/
static void oled_show_char(uint8_t x, uint8_t y, uint8_t ch, uint8_t size, uint8_t mode)
{
    uint8_t temp, t, t1;
    uint8_t y0 = y;
    uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);

    if (size == FONTSIZE_32_16)
    {
        //ch = (ch-48)*2;
        ch = (ch - ' ') * 2; /* 因为32大小的字体每个占用2个数组元素，所以每次步进2 */
    }
    else
    {
        ch = ch - ' ';
    }

    for (t = 0; t < csize; t++)
    {
        if (size == FONTSIZE_12_6)
            temp = oled_asc2_1206[ch][t]; //调用1206字体
        else if (size == FONTSIZE_16_8)
            temp = oled_asc2_1608[ch][t]; //调用1608字体
        else if (size == FONTSIZE_32_16)
            temp = oled_asc2_3216[ch][t];
        else
            return;

        for (t1 = 0; t1 < 8; t1++)
        {
            if (temp & 0x80)
                oled_drow_point(x, y, mode);
            else
                oled_drow_point(x, y, !mode);

            temp <<= 1;
            y++;
            if ((y - y0) == size)
            {
                y = y0;
                x++;
                break;
            }
        }
    }
}

/***********************************************************************
 *  函数名: oled_show_number
 *  描述：在缓冲区打印数字
 *  参数：uint8_t x 横坐标
 *  参数：uint8_t y 纵坐标(1-64)
 *  参数: uint32_t num 要显示的数字
 *  参数: uint8_t  len 数字的长度 
 *  参数：uint8_t mode 显示模式(1:黑底白字 0：白底黑字)
 *  返回值： void
 ***********************************************************************/
static void oled_show_number(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size, uint8_t mode)
{
    uint8_t t, temp;
    uint8_t enshow = 0;
    for (t = 0; t < len; t++)
    {
        temp = (num / oled_pow(10, len - t - 1)) % 10;
        if (enshow == 0 && t < (len - 1))
        {
            if (temp == 0)
            {
                oled_show_char(x + (size / 2) * t, y, '0', size, mode);
                continue;
            }
            else
                enshow = 1;
        }
        oled_show_char(x + (size / 2) * t, y, temp + '0', size, mode);
    }
}

/***********************************************************************
 *  函数名: oled_show_string
 *  描述：在缓冲区打印字符串
 *  参数：uint8_t x 横坐标
 *  参数：uint8_t y 纵坐标(1-64)
 *  参数: const uint8_t *p 要显示的字符串指针
 *  参数: size_t size 字体大小 
 *  参数：uint8_t mode 显示模式(1:黑底白字 0：白底黑字)
 *  返回值： void
 ***********************************************************************/
static void oled_show_string(uint8_t x, uint8_t y, const uint8_t *p, size_t size, uint8_t mode)
{
    while (*p != '\0')
    {
        if (x > MAX_CHAR_POSX)
        {
            x = 0;
            y += size;
        }
        if (y > MAX_CHAR_POSY)
        {
            y = x = 0;
            oled_clear(i2c_num);
        }

        oled_show_char(x, y, *p, size, mode);
        x += (size / 2);
        p++;
    }
}

/***********************************************************************
 *  函数名: oled_draw_bmp
 *  描述：以page为单位在缓冲区绘画一张bmp图片，速度快，但是不灵活(0-8) 融合的形式
 *  参数：uint8_t x 横坐标
 *  参数：uint8_t y 纵坐标(0-8)
 *  参数: uint8_t w bmp宽度(竖着总量/8)
 *  参数: uint8_t l bmp长度
 *  参数: uint8_t model 模式： 0 混合(融合)模式； 1：替换(覆盖))模式;
 *  参数：void *bmp bmp资源
 ***********************************************************************/
static void oled_draw_bmp(uint8_t x, uint8_t y, uint8_t w, uint8_t l, uint8_t model, void *bmp)
{
    uint8_t m, n = 0;
    uint8_t(*tmp)[w] = (uint8_t(*)[w])bmp;
    for (n = 0; n < w; n++)
    {
        for (m = 0; m < l; m++)
        {
            if (model)
                OLED_GRAM[m + x][7 - n + y] = tmp[m][n];
            else
                OLED_GRAM[m + x][7 - n + y] |= tmp[m][n];
        }
    }
}

/***********************************************************************
 *  函数名: oled_draw_bmp_point
 *  描述：在像素为单位缓冲区绘画一张bmp图片，速度慢，但是灵活(0-64)
 *  参数：uint8_t x 横坐标
 *  参数：uint8_t y 纵坐标(0-64)
 *  参数: uint8_t w bmp宽度(竖着总量/8)
 *  参数: uint8_t l bmp长度
 *  参数：void *bmp bmp资源
 *  参数：uint8_t mode 是否反色（1：白底黑图 0：黑底白图Y）
 ***********************************************************************/
static void oled_draw_bmp_point(uint8_t x, uint8_t y, uint8_t w, uint8_t l, uint8_t mode, void *bmp)
{
    uint8_t m, n = 0, t1 = 0;
    uint8_t temp = 0;
    uint8_t y0 = y;
    uint8_t x0 = x;
    uint8_t(*tmp)[w] = (uint8_t(*)[w])bmp;

    for (n = 0; n < w; n++)
    {
        for (m = 0; m < l; m++)
        {
            temp = tmp[m][n];
            for (t1 = 0; t1 < 8; t1++)
            {
                if (temp & 0x80)
                    oled_drow_point(x, y, mode);
                else
                    oled_drow_point(x, y, !mode);

                temp <<= 1;
                y++;
            }
            x++;
            y = y0;
        }
        x = x0;
        y += 8;
        y0 += 8;
    }
}

/***********************************************************************
 *  函数名: oled_draw_bmp_reversal
 *  描述：在缓冲区 垂直反转 绘画一张bmp图片
 *  参数：uint8_t x 横坐标
 *  参数：uint8_t y 纵坐标
 *  参数: uint8_t w bmp宽度(竖着总量/8)
 *  参数: uint8_t l bmp长度
 *  参数: uint8_t model 模式： 0 混合(融合)模式； 1：替换(覆盖))模式;
 *  参数：void *bmp bmp资源
 ***********************************************************************/
static void oled_draw_bmp_reversal(uint8_t x, uint8_t y, uint8_t w, uint8_t l, uint8_t model, void *bmp)
{
    uint8_t m, n = 0;
    uint8_t(*tmp)[w] = (uint8_t(*)[w])bmp;
    for (n = 0; n < w; n++)
    {
        for (m = 0; m < l; m++)
        {
            if (model)
                OLED_GRAM[l + x - m - 1][7 - n + y] = tmp[m][n];
            else
                OLED_GRAM[l + x - m - 1][7 - n + y] |= tmp[m][n];
        }
    }
}
